﻿using System;
using System.Collections.Concurrent;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.CacheService;
using Fuse8.DomainFramework.Common;

namespace Fuse8.DomainFramework.Configuration.Cache
{
    /// <summary>
    /// Cache containers configuration
    /// <example>    
    /// <configuration>
    ///   <configSections>
    ///     <sectionGroup name="fuse8.domainframework">
    ///       <section name="cacheContainers" type="Fuse8.DomainFramework.Configuration.Cache.CacheServiceConfigurationHandler, Fuse8.DomainFramework" />
    ///     </sectionGroup>
    ///   </configSections>
    ///
    ///   <fuse8.domainframework>
    ///     <cache>
    ///         <cacheContainers>
    ///             <add name="1" main="true" containerType="Memcached" successorContainerName="2" absoluteExpiration="" />
    ///             <add name="2" containerType="HttpCache" enabled="false" async="false" />  
    ///             <add name="3" containerType="NoCache" forType="" />
    ///             <add name="4" containerType="DependencyCache" forType="" dependency="Entity_1,Entity_2"/>
    ///         </cacheContainers>    
    ///     </cache>
    ///   </fuse8.domainframework>
    /// </configuration>
    /// </example>
    /// </summary>
    public static class CacheServiceConfigurationManager
    {
        #region Field

        private static CacheServiceConfigurationHandler _config;

        private static List<CacheContainerElement> _commonCacheContainers = new List<CacheContainerElement>();
        private static Dictionary<string, CacheContainerElement> _privateCacheContainers = new Dictionary<string, CacheContainerElement>();

        #endregion

        #region Properties

        /// <summary>
        /// Cache containers collection
        /// </summary>
        public static CacheServiceConfigurationHandler Config
        {
            get
            {
                if (_config == null)
                {
                    _config = ConfigurationManager.GetSection("fuse8.domainframework/cache") as CacheServiceConfigurationHandler;
                }

                return _config;
            }
            set
            {
                _config = value;

                InitConfig();
            }
        }

        internal static List<CacheContainerElement> CommonCacheContainers
        {
            get
            {
                return _commonCacheContainers;
            }
        }

        internal static Dictionary<string, CacheContainerElement> PrivateCacheContainers
        {
            get
            {
                return _privateCacheContainers;
            }
        }

        #endregion       
 
        #region Constructor

        static CacheServiceConfigurationManager()
        {
            InitConfig();
        }

        #endregion

        #region Private methods

        private static void InitConfig()
        {
            if (Config != null)
            {
                foreach (CacheContainerElement cacheContainer in Config.CacheContainers)
                {
                    if (cacheContainer.Enabled)
                    {
                        if (string.IsNullOrEmpty(cacheContainer.ForType))
                        {
                            _commonCacheContainers.Add(cacheContainer);
                        }
                        else
                        {
                            if (CanAddPrivateCacheContainerAsMain(cacheContainer))
                            {
                                _privateCacheContainers.Add(cacheContainer.ForType, cacheContainer);
                            }
                        }
                    }
                }
            }
        }

        private static bool CanAddPrivateCacheContainerAsMain(CacheContainerElement cacheContainer)
        {
            var count = Config.CacheContainers.Cast<CacheContainerElement>().Count(p=>p.ForType==cacheContainer.ForType);
            return !_privateCacheContainers.ContainsKey(cacheContainer.ForType) &&
                   (cacheContainer.Main && count > 1 || !cacheContainer.Main && count <= 1);
        }

        #endregion
    }

    internal static class CacheServiceFactory
    {
        private static readonly ConcurrentDictionary<string, IEntityCacheServiceElement> _cacheRegistry = new ConcurrentDictionary<string, IEntityCacheServiceElement>();

        public static void AddCacheServiceToRegistry(string entityType, IEntityCacheServiceElement serviceElement)
        {
            Guard.ArgumentNotEmpty(entityType, "entityType");
            Guard.ArgumentNotNull(serviceElement, "serviceElement");

            _cacheRegistry.TryAdd(entityType, serviceElement);
        }

        public static IEntityCacheServiceElement GetCacheServiceElement(string forType)
        {
            Guard.ArgumentNotEmpty(forType, "forType");

            IEntityCacheServiceElement serviceElement;
            return !_cacheRegistry.TryGetValue(forType, out serviceElement) ? null : serviceElement;
        }
    }

    internal static class CacheServiceFactory<TEntity, TKey> 
        where TEntity : class, IEntityObject<TKey>, new()
        where TKey : IComparable<TKey>, IEquatable<TKey> 
    {
        private static IEntityCacheService<TEntity, TKey> _service;        
        
        #region Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static IEntityCacheService<TEntity, TKey> GetCacheService()
        {
            if (_service == null)
            {
                _service = InitCacheService();
            }

            return _service;
        }

        private static IEntityCacheService<TEntity, TKey> InitCacheService(string configName = null)
        {
            IEntityCacheService<TEntity, TKey> result = null;

            var config = CacheServiceConfigurationManager.Config;

            if (config != null)
            {
                IEntityCacheService<TEntity, TKey> service = null;

                var cacheServiceOptions = new CacheServiceOptions();

                CacheContainerElement cacheContainerElement = null;
                string typeName = typeof(TEntity).Name;

                if (string.IsNullOrEmpty(configName))
                {
                    cacheContainerElement = CacheServiceConfigurationManager.PrivateCacheContainers.ContainsKey(typeName)
                                                ? CacheServiceConfigurationManager.PrivateCacheContainers[typeName]
                                                : CacheServiceConfigurationManager.CommonCacheContainers.Exists(p => p.Main)
                                                      ? CacheServiceConfigurationManager.CommonCacheContainers.First(p => p.Main)
                                                      : CacheServiceConfigurationManager.CommonCacheContainers.Count > 0
                                                            ? CacheServiceConfigurationManager.CommonCacheContainers.First() : null;
                }
                else
                {
                    cacheContainerElement = config.CacheContainers[configName];
                }

                if (cacheContainerElement != null)
                {
                    if (cacheContainerElement.AbsoluteExpiration != TimeSpan.Zero)
                    {
                        cacheServiceOptions.AbsoluteTimeSpanExpiration = cacheContainerElement.AbsoluteExpiration;
                    }
                    cacheServiceOptions.UseAsyncMethods = cacheContainerElement.Async;
                    cacheServiceOptions.Dependencies = 
                        cacheContainerElement.Dependency.Split(new char[','], StringSplitOptions.RemoveEmptyEntries).ToList();

                    service = MakeCacheService(cacheContainerElement.ContainerType, cacheServiceOptions);
                    if (service != null)
                    {                        
                        result = service;
                    }

                    if (!string.IsNullOrEmpty(cacheContainerElement.SuccessorContainerName))
                    {
                        IEntityCacheService<TEntity, TKey> successor = InitCacheService(cacheContainerElement.SuccessorContainerName);
                        if (successor != null && result != null)
                        {
                            result.SetSuccessor(successor);                            
                        }
                    }
                }

                if (result != null)
                {
                    CacheServiceFactory.AddCacheServiceToRegistry(typeName, result);
                }
            }

            return result;
        }

        #endregion

        private static IEntityCacheService<TEntity, TKey> MakeCacheService(string type, CacheServiceOptions cacheServiceOptions)
        {
            IEntityCacheService<TEntity, TKey> result = null;

            string typePattern = string.Empty;
            Type typeOfCacheService = null;

            var typeParts = type.Split(new char[] { ',' });
            if (typeParts != null && typeParts.Length > 0)
            {
                StringBuilder sb = new StringBuilder();

                sb.AppendFormat("{0}`2", typeParts[0]);

                if (typeParts.Length > 1)
                {
                    for (int i = 1; i < typeParts.Length; i++)
                    {
                        sb.AppendFormat(", {0}", typeParts[i]);
                    }
                }

                typePattern = sb.ToString();
            }
            else
            {
                typePattern = string.Format(CultureInfo.InvariantCulture, "{0}`2", type);
            }

            typeOfCacheService = Type.GetType(typePattern);

            if (typeOfCacheService == null)
            {
                throw new ConfigurationErrorsException(string.Format("{0} isn't known type", typePattern));
            }

            typeOfCacheService = typeOfCacheService.MakeGenericType(new Type[] { typeof(TEntity), typeof(TKey) });

            if (typeOfCacheService != null)
            {
                result = Activator.CreateInstance(typeOfCacheService, new object[] { cacheServiceOptions })
                    as IEntityCacheService<TEntity, TKey>;
            }

            return result;
        }
    }
}
